package com.joinway.framework.data.web.controller;

import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.jsondoc.core.annotation.Api;
import org.jsondoc.core.annotation.ApiBodyObject;
import org.jsondoc.core.annotation.ApiMethod;
import org.jsondoc.core.annotation.ApiQueryParam;
import org.jsondoc.core.annotation.ApiResponseObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import com.joinway.framework.bean.constant.ErrorCodeConstants;
import com.joinway.framework.bean.domain.constant.SqlConstants;
import com.joinway.framework.bean.exception.InternalException;
import com.joinway.framework.bean.logging.annotation.InputLog;
import com.joinway.framework.bean.logging.annotation.OutputLog;
import com.joinway.framework.bean.view.View;
import com.joinway.framework.data.jdbc.bean.ConditionItem;
import com.joinway.framework.data.jdbc.bean.Criteria;
import com.joinway.framework.data.jdbc.bean.OrderItem;
import com.joinway.framework.data.jdbc.bean.QueryCriteria;
import com.joinway.framework.data.jdbc.bean.SaveCriteria;
import com.joinway.framework.data.jdbc.repository.TableRepository;
import com.joinway.framework.data.jdbc.service.SqlService;
import com.joinway.framework.data.web.bean.view.QueryResultSet;
import com.joinway.framework.data.web.bean.view.SaveResultSet;
import com.joinway.framework.data.web.constant.DataConstants;
import com.joinway.framework.extension.groovy.DomainUtils;
import com.joinway.framework.extension.groovy.GroovyUtils;
import com.joinway.framework.extension.net.json.JsonConverter;
import com.joinway.framework.extension.utils.DataUtils;

@Api(name = "RMDB SQL通用接口", description = "")
@RestController
@RequestMapping("sql")
public class SqlController {
	private final static Logger log = LoggerFactory.getLogger(SqlController.class);
	
	@Autowired protected TableRepository repository;
	
	protected SqlService service;
	
	@PostConstruct public void init(){
		service = new SqlService(repository);
	}
	
//	@RequestMapping(value="/test.json", produces=MediaType.APPLICATION_JSON_VALUE)
//	@OutputLog
//	public View test(){
//		return new View();
//	}
	
	@ApiMethod(description="查询", consumes=MediaType.APPLICATION_JSON_VALUE, produces=MediaType.APPLICATION_JSON_VALUE)
	@ApiResponseObject
	@RequestMapping(value="/query.json", method=RequestMethod.POST, consumes=MediaType.APPLICATION_JSON_VALUE, produces=MediaType.APPLICATION_JSON_VALUE)
	@InputLog
	@OutputLog
	public QueryResultSet select(@RequestBody @ApiBodyObject QueryCriteria criteria) throws Exception{
		validate(criteria);
		
		initQueryCriteria(criteria);
		
		QueryResultSet result = new QueryResultSet();
		
		result.setCount(service.selectCount(criteria));
		
		List<Map<String, ?>> rows = DomainUtils.convertToRowMaps(service.select(criteria));
		result.setRows(rows);
		
		GroovyUtils.removeASTFields(result);
		
		return result;
	}
	
	@ApiMethod(description="查询", produces=MediaType.APPLICATION_JSON_VALUE)
	@ApiResponseObject
	@RequestMapping(value="/plain/query.json", produces=MediaType.APPLICATION_JSON_VALUE)
	@InputLog
	@OutputLog
	public QueryResultSet select(@ApiQueryParam(name="sql", description="sql语句") @RequestParam("sql") String sql) throws Exception{
		QueryCriteria criteria = JsonConverter.jsonToObject(sql, QueryCriteria.class);
		
		validate(criteria);
		
		initQueryCriteria(criteria);
		
		QueryResultSet result = new QueryResultSet();
		
		result.setCount(service.selectCount(criteria));
		
		List<Map<String, ?>> rows = DomainUtils.convertToRowMaps(service.select(criteria));
		result.setRows(rows);
		
		GroovyUtils.removeASTFields(result);
		
		return result;
	}
	
	@ApiMethod(description="查询", produces=MediaType.APPLICATION_JSON_VALUE)
	@ApiResponseObject
	@RequestMapping(value="/native/query.json", produces=MediaType.APPLICATION_JSON_VALUE)
	@InputLog
	@OutputLog
	public QueryResultSet selectRawSql(@ApiQueryParam(name="sql", description="原生sql语句") @RequestParam("sql") String sql) throws Exception{
		QueryResultSet result = new QueryResultSet();
		
		
		List<?> domains = service.select(sql);
		result.setCount(DataUtils.getSize(domains));
		
		List<Map<String, ?>> rows = DomainUtils.convertToRowMaps(domains);
		result.setRows(rows);
		
		GroovyUtils.removeASTFields(result);
		
		return result;
	}
	
	@ApiMethod(description="插入或更新", consumes=MediaType.APPLICATION_JSON_VALUE, produces=MediaType.APPLICATION_JSON_VALUE)
	@ApiResponseObject
	@RequestMapping(value="/save.json", method=RequestMethod.POST, produces=MediaType.APPLICATION_JSON_VALUE)
	@InputLog
	@OutputLog
	public SaveResultSet save(@RequestBody @ApiBodyObject SaveCriteria criteria) throws Exception {
		validate(criteria);
		
		initSaveCriteria(criteria);
		
		SaveResultSet result = new SaveResultSet();
		
		result.setCount(service.persist(criteria));
		
		return result;
	}
	
	@ApiMethod(description="删除", consumes=MediaType.APPLICATION_JSON_VALUE, produces=MediaType.APPLICATION_JSON_VALUE)
	@ApiResponseObject
	@RequestMapping(value="/delete.json", method=RequestMethod.POST, produces=MediaType.APPLICATION_JSON_VALUE)
	@InputLog
	@OutputLog
	public SaveResultSet delete(@RequestBody @ApiBodyObject Criteria criteria) throws Exception {
		validate(criteria);
		
		initCriteria(criteria);
		
		SaveResultSet result = new SaveResultSet();
		
		result.setCount(service.delete(criteria));
		
		return result;
	}
	
	@ExceptionHandler(Exception.class)
	@ResponseBody
	public View handle(Exception e) {
		log.error("fatal error", e);
		
		View view = new View();
		view.setCode(ErrorCodeConstants.INTERNAL_ERROR);
		view.setMessage(ErrorCodeConstants.INTERNAL_ERROR_DESC);
		view.setError(e.getMessage());
		
		return view;
	}

	protected boolean validate(Criteria criteria) throws InternalException {
		if(criteria == null || StringUtils.isBlank(criteria.getTable())){
			throw new InternalException(DataConstants.NO_TABLE, DataConstants.NO_TABLE_DESC, DataConstants.NO_TABLE_DESC);
		}

		return true;
	}
	
	protected void initQueryCriteria(QueryCriteria criteria){
		initCriteria(criteria);
		
		if(criteria.getFields() == null){
			criteria.setFields(SqlConstants.Key.All);
		}
		
		if(CollectionUtils.isNotEmpty(criteria.getOrder())){
			for(OrderItem o : criteria.getOrder()){
				if(StringUtils.isBlank(o.getSort())){
					o.setSort(SqlConstants.Order.Asc);
				}
			}
//			criteria.getOrder().forEach(o -> StringUtils.isBlank(o.getSort()) && o.setSort(SqlConstants.Order.Asc))));
		}
	}

	protected void initSaveCriteria(SaveCriteria criteria){
		initCriteria(criteria);
		
		if(StringUtils.isBlank(criteria.getAction())){
			criteria.setAction( SqlConstants.PersistenceAction.UpdateOrInsert);
		}
	}
	
	protected void initCriteria(Criteria criteria){
		if(StringUtils.isBlank(criteria.getCondition())){
			criteria.setCondition(SqlConstants.Condition.And);
		}
		
		if(StringUtils.isBlank(criteria.getDbType())){
			criteria.setDbType(SqlConstants.DataBase.MySql);
		}
		
		if(CollectionUtils.isNotEmpty(criteria.getItems())){
			Iterator<ConditionItem> items = criteria.getItems().iterator();
			
			while(items.hasNext()){
				ConditionItem it = items.next();
				if(StringUtils.isBlank(it.getType())){
					it.setType(SqlConstants.DataType.Text);
				}
				if(StringUtils.isBlank(it.getOperator())){
					it.setOperator(SqlConstants.Operator.Equals);
				}
				if(StringUtils.isBlank(it.getCondition())){
					it.setCondition(SqlConstants.Condition.And);
				}
			}
		}
	}
}
